pragma Ada_2012;
with Ada.Unchecked_Deallocation;
with Ada.Finalization;
with Ada.Containers, Ada.Containers.Vectors;
use Ada.Containers;
package Storage_Streams.Unbounded is

   -- create memory stream with unbounded capacity
   -- if more memory required, grow by 1024 bytes
   type Unbounded_Stream_Type is new Storage_Stream_Type with private;

   overriding
   procedure Clear (this : in out Unbounded_Stream_type)
      with Post => this.Capacity = 0;

   overriding
   procedure Reset_For_Read (this : in out Unbounded_Stream_type);

   overriding
   procedure Reset_For_Write (this : in out Unbounded_Stream_type);

   overriding
   procedure Read (
      this : in out Unbounded_Stream_type;
      Item : out Stream_Element_Array;
      Last : out Stream_Element_Offset
      );

   overriding
   procedure Write ( 
      this : in out Unbounded_Stream_type;
      Item : Stream_Element_Array 
      );

   overriding
   function Read_Position (
      this : Unbounded_Stream_Type 
      ) return Stream_Element_Offset;

   overriding
   function Write_Position (
      this : Unbounded_Stream_Type 
      ) return Stream_Element_Offset;

   overriding
   procedure Set_Read_Position (
      this     : in out Unbounded_Stream_Type;
      Position : Stream_Element_Offset
      );

   overriding
   procedure Set_Write_Position (
      this     : in out Unbounded_Stream_Type;
      Position : Stream_Element_Offset
      );

   function Capacity (
      this : Unbounded_Stream_Type
      ) return Stream_Element_Count;
   -- return current capacity

private

   type Block_Type (Size : Stream_Element_Count);
   type Block_Pointer is access Block_Type;

   type Block_Type (Size : Stream_Element_Count)  is record
      Data : Stream_Element_Array(1 .. Size);
   end record;

   procedure Free is new Ada.Unchecked_Deallocation(Block_Type, Block_Pointer);

   -- block_index_type range must in count_type range
   subtype Block_Index_Type is Stream_Element_Offset
   range 1 .. Stream_Element_Count(Count_Type'Last);
--   with Dynamic_Predicate => Block_Index_Type'Size <= Count_Type'Size;

   package Block_Vectors is new Vectors(Block_Index_Type, Block_Pointer);

   type Stream_Cursor_Type is record
      Block : Stream_Element_Offset;
      Index : Stream_Element_Offset;
   end record;

   function "<" (left, right : Stream_Cursor_Type) return Boolean;

   function ">" (left, right : Stream_Cursor_Type) return Boolean;

   Null_Stream_Cursor : constant Stream_Cursor_Type := (0, 0);
   Null_Stream_First  : constant Stream_Cursor_Type := (0, 1);

   type Unbounded_Destructor_Type (
      Owner : not null access Unbounded_Stream_Type
      ) is new Ada.Finalization.Limited_Controlled with null record;

   procedure Finalize (this : in out Unbounded_Destructor_Type);

   type Unbounded_Stream_Type is new Storage_Stream_Type with record 
      F_Block_Size  : Stream_Element_Count := 1024;
      F_Blocks      : Block_Vectors.Vector;
      F_Data_Last   : Stream_Cursor_Type := Null_Stream_Cursor;
      F_Read_Index  : Stream_Cursor_Type := Null_Stream_First;
      F_Write_Index : Stream_Cursor_Type := Null_Stream_First;
      F_Destructor  : Unbounded_Destructor_Type(Unbounded_Stream_Type'Access);
   end record;

   procedure Clear_Data (this : in out Unbounded_Stream_Type);

   procedure Adjust_Capacity (this : in out Unbounded_Stream_Type);

   procedure Update_Length (this : in out Unbounded_Stream_Type);

   function End_Of_Data (
      this   : in out Unbounded_Stream_Type;
      Cursor : Stream_Cursor_Type
      ) return Boolean;

   function End_Of_List (
      this   : in out Unbounded_Stream_Type;
      Cursor : Stream_Cursor_Type
      ) return Boolean;

   function First (
      this : Unbounded_Stream_Type
      ) return Stream_Cursor_Type;

   function Last (
      this : Unbounded_Stream_Type
      ) return Stream_Cursor_Type;

   function Next (
      this : Unbounded_Stream_Type;
      c    : Stream_Cursor_Type
      ) return Stream_Cursor_Type;

   function Previous (
      this : Unbounded_Stream_Type;
      c    : Stream_Cursor_Type
      ) return Stream_Cursor_Type;

   function Position (
      this : Unbounded_Stream_Type;
      c    : Stream_Cursor_Type
      ) return Stream_Element_Offset;

end Storage_Streams.Unbounded;
